#define _XOPEN_SOURCE 500

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <log.h>

#include "scu.h"
#include "sculist.h"

extern log_default_streams_t scul_list;

scu_list_t scuListCreate(int size)
{
  scu_list_t list;

  list.size = size;
  list.start = NULL;

  scos_mutex_init(&list.lock);

  return list;
}

void scuListFree(scu_list_t *list)
{
  scu_listitem_t *r, *p;

  scuListLock(list);

  r = list->start;
  while (r)
  {
    p = r;
    r = r->next;
    if (p->owned) free(p->data);
    free(p);
  }

  list->start = NULL;

  scuListUnlock(list);

  scos_mutex_destroy(&list->lock);
}

void scuListLock(scu_list_t *list)
{
  scos_mutex_lock(&list->lock);
}

void scuListUnlock(scu_list_t *list)
{
  scos_mutex_unlock(&list->lock);
}

int scuListGetLength(scu_list_t *list)
{
  scu_listitem_t *r;
  int i=0;

  scuListLock(list);

  for (r = list->start; r; r = r->next, i++);

  scuListUnlock(list);
    
  return i;
}

void *scuListAddItem(scu_list_t *list)
{
  scu_listitem_t *r=NULL, *p=NULL, *n=NULL;
  void *retval=NULL;
  int id=0;

  scuListLock(list);

  if (!list->start)
  {
    /* List is empty */
    abortOnNULL(STREAM(scul_list.err),
		n = list->start =
		(scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
    n->next = NULL;
  }
  else
  {
    /* Find first available id */
    id = -1;
    for (r=list->start; r; p = r, r = r->next)
    {
      if ((*((int*)r->data) - id) > 1)
      {
	id++;
	if (!p)
	{
	  /* Add at the beginning of the list */
	  abortOnNULL(STREAM(scul_list.err),
		      n = list->start =
		      (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
	  n->next = r;
	  break;
	}
	else
	{
	  /* Add in the middle of the list */
	  abortOnNULL(STREAM(scul_list.err),
		      n = (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
	  p->next = n;
	  n->next = r;
	  break;
	}
      }
      id = *((int*)r->data);
    }

    if (!n)
    {
      /* Add at the end of the list */
      id++;
      abortOnNULL(STREAM(scul_list.err),
		  n = (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
      p->next = n;
      n->next = NULL;
    }
  }

  abortOnNULL(STREAM(scul_list.err), retval = n->data = calloc(1, list->size));
  *((int*)n->data) = id;
  n->owned = 1;

  scuListUnlock(list);

  return retval;
}

/* DANGER, WILL ROBINSON: ID WON'T MATCH: ITEM CANNOT BE PART OF TWO
 * LISTS AT ONCE
 */
void *scuListAddPreallocatedItem(scu_list_t *list, void *data, int owned)
{
  scu_listitem_t *r=NULL, *p=NULL, *n=NULL;
  void *retval=NULL;
  int id=0;

  scuListLock(list);

  if (!list->start)
  {
    /* List is empty */
    abortOnNULL(STREAM(scul_list.err),
		n = list->start =
		(scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
    n->next = NULL;
  }
  else
  {
    /* Find first available id */
    id = -1;
    for (r=list->start; r; p = r, r = r->next)
    {
      if ((*((int*)r->data) - id) > 1)
      {
	id++;
	if (!p)
	{
	  /* Add at the beginning of the list */
	  abortOnNULL(STREAM(scul_list.err),
		      n = list->start =
		      (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
	  n->next = r;
	  break;
	}
	else
	{
	  /* Add in the middle of the list */
	  abortOnNULL(STREAM(scul_list.err),
		      n = (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
	  p->next = n;
	  n->next = r;
	  break;
	}
      }
      id = *((int*)r->data);
    }

    if (!n)
    {
      /* Add at the end of the list */
      id++;
      abortOnNULL(STREAM(scul_list.err),
		  n = (scu_listitem_t*) malloc(sizeof(scu_listitem_t)));
      p->next = n;
      n->next = NULL;
    }
  }

  retval = n->data = data;
  n->owned = owned;

  scuListUnlock(list);

  return retval;
}


void *scuListGetItem(scu_list_t *list, int id)
{
  scu_listitem_t *r;
  void *retval=NULL;

  scuListLock(list);

  for (r = list->start; r; r = r->next)
  {
    if (*((int*)r->data) == id)
    {
      retval = r->data;
      break;
    }
    else if (*((int*)r->data) > id)
      break;
  }

  scuListUnlock(list);

  return retval;
}

void *scuListFindItem(scu_list_t *list, int offset, scu_vartype_t type, ...)
{
  va_list ap;
  char *s;
  int i;
  void *p;
  scu_listitem_t *r=NULL;
  void *retval=NULL;

  va_start(ap, type);

  scuListLock(list);

  switch (type)
  {
    case SCU_VAR_POINTER:
      p = va_arg(ap, void*);
      for (r = list->start; r; r = r->next)
      {
	if (!memcmp((char*)r->data+offset, p, sizeof(void*)))
	  break;
      }
      break;
    case SCU_VAR_INTEGER: 
      i = va_arg(ap, int);
      for (r = list->start; r; r = r->next)
      {
	if (!memcmp((char*)r->data+offset, &i, sizeof(int)))
	  break;
      }      
      break;
    case SCU_VAR_STRING:
      s = va_arg(ap, char*);
      for (r = list->start; r; r = r->next)
      {
	if (!strcmp((char*)r->data+offset, s))
	  break;
      }      
      break;      
  }

  va_end(ap);

  if (r) retval = r->data;

  scuListUnlock(list);

  return retval;
}

void scuListRemoveItem(scu_list_t *list, int id)
{
  scu_listitem_t *r, *p=NULL;

  scuListLock(list);

  for (r = list->start; r; p = r, r = r->next)
  {
    if (*((int*)r->data) == id)
    {
      if (!p)
      {
        /* Delete from start of list */
        list->start = r->next;
      }
      else
      {
        /* Delete from middle or end of list */
        p->next = r->next;
      }

      if (r->owned) free(r->data);
      free(r);
      break;
    }
  }

  scuListUnlock(list);
}

void *scuListEnumerateItems(scu_list_t *list, scu_listitem_t **item)
{
  void *retval = NULL;

  scuListLock(list);

  if (*item)
    *item = (*item)->next;
  else
    *item = list->start;
  
  if (*item) retval = (*item)->data;

  scuListUnlock(list);

  return retval;
}
